Skip to content

fix(genesis-writer): emit release_date as RFC3339, not Postgres text - #519

Merged
rickyrombo merged 1 commit into
mainfrom
mjp-genesis-writer-release-date-rfc3339
Aug 17, 2026
Merged

fix(genesis-writer): emit release_date as RFC3339, not Postgres text#519
rickyrombo merged 1 commit into
mainfrom
mjp-genesis-writer-release-date-rfc3339

Conversation

@rickyrombo

Copy link
Copy Markdown
Contributor

Unreleased tracks are published early

The tracks query selected release_date::text, which yields Postgres's own
2026-09-06 22:06:00. The indexer's parseReleaseDate accepts RFC3339,
RFC3339Nano and Mon Jan 02 2006 15:04:05 GMT-0700none of which match — so
releaseDateOrDefault silently fell back to block time (the source row's created_at).

That is not a cosmetic date difference. One format mismatch produces three symptoms:

  1. release_date is rewritten from the future into the past
  2. ScheduledReleasePublisher then matches is_unlisted = true AND is_scheduled_release = true AND release_date <= now() and sets is_unlisted = false
  3. …and updated_at = now(), which is why some rows carried the replay wall-clock

Measured on the 2026-08-07 snapshot

Unlisted tracks with a future release date, rewritten 372
…of those, published early (is_unlisted flipped) 368
Tracks still holding a future release date 498 → 136

Verified end to end — track 2073330890

source        release_date 2026-09-06 22:06:00   is_unlisted true
migration tx  release_date 2026-09-06 22:06:00   is_unlisted true    <- writer carried both
indexed row   release_date 2025-08-10 20:57:26   is_unlisted false   <- exactly its created_at

Fix

Scan release_date as a timestamp and format it RFC3339. Every other timestamp
the writer emits already goes out as RFC3339 — this one was the outlier.

TestReleaseDateIsEmittedInAnAcceptedLayout asserts the output parses under the
layout the indexer actually uses, and that the Postgres text shape cannot come back.

🤖 Generated with Claude Code

The tracks query selected release_date::text, which yields Postgres's own
"2026-09-06 22:06:00". The indexer's parseReleaseDate accepts RFC3339,
RFC3339Nano and "Mon Jan 02 2006 15:04:05 GMT-0700" -- none of which match --
so releaseDateOrDefault silently fell back to block time, i.e. the source row's
created_at.

That is not a cosmetic date difference. A track whose release_date lands in the
past is then picked up by ScheduledReleasePublisher, which sets
is_unlisted = false and updated_at = now(). Measured on the 2026-08-07 snapshot:

  372  unlisted tracks with a future release_date had the date rewritten
  368  of them were published early
  498 -> 136  tracks still holding a future release_date

Verified end to end on track 2073330890: source release_date 2026-09-06
22:06:00 and is_unlisted true, the transaction carried both correctly, and the
indexed row came out release_date 2025-08-10 20:57:26 (exactly its created_at)
with is_unlisted false.

Every other timestamp the writer emits already goes out as RFC3339; this one
was the outlier.
@rickyrombo
rickyrombo merged commit 5a8d4a4 into main Aug 17, 2026
5 checks passed
@rickyrombo
rickyrombo deleted the mjp-genesis-writer-release-date-rfc3339 branch August 17, 2026 17:42
rickyrombo pushed a commit that referenced this pull request Aug 20, 2026
time.RFC3339 has no fractional-second component, so every release_date
carrying microseconds was rounded down to the whole second on the way out.
A full-snapshot replay compared against its source showed 26,129 current
tracks affected, e.g. 2026-02-02T15:53:12.050585Z emitted as
2026-02-02T15:53:12Z.

The indexer already accepts RFC3339Nano, and a value with no fractional
part formats identically under both layouts, so rows without microseconds
are unchanged.

This is a different failure from the one #519 fixed. That emitted
Postgres text format, which no accepted layout matched, so the indexer
fell back to block time. This parses cleanly and quietly rounds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rickyrombo added a commit that referenced this pull request Aug 20, 2026
time.RFC3339 has no fractional-second component, so every release_date
carrying microseconds was rounded down to the whole second on the way out.
A full-snapshot replay compared against its source showed 26,129 current
tracks affected, e.g. 2026-02-02T15:53:12.050585Z emitted as
2026-02-02T15:53:12Z.

The indexer already accepts RFC3339Nano, and a value with no fractional
part formats identically under both layouts, so rows without microseconds
are unchanged.

This is a different failure from the one #519 fixed. That emitted
Postgres text format, which no accepted layout matched, so the indexer
fell back to block time. This parses cleanly and quietly rounds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rickyrombo added a commit that referenced this pull request Aug 20, 2026
…tamp (#534)

* fix(genesis-writer): keep sub-second precision on release_date

time.RFC3339 has no fractional-second component, so every release_date
carrying microseconds was rounded down to the whole second on the way out.
A full-snapshot replay compared against its source showed 26,129 current
tracks affected, e.g. 2026-02-02T15:53:12.050585Z emitted as
2026-02-02T15:53:12Z.

The indexer already accepts RFC3339Nano, and a value with no fractional
part formats identically under both layouts, so rows without microseconds
are unchanged.

This is a different failure from the one #519 fixed. That emitted
Postgres text format, which no accepted layout matched, so the indexer
fell back to block time. This parses cleanly and quietly rounds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(genesis-writer): keep sub-second precision on every emitted timestamp

time.RFC3339 has no fractional-second component, so every timestamp the
writer emitted was rounded down to the whole second. Verified against a
full-snapshot replay:

  track_downloads   78,032 of 78,032 source rows carry sub-second
                    created_at; the migrated table has zero
  subscriptions  1,022,554        playlist_tracks  1,014,903
  saves            380,604        reposts            361,803
  follows          104,614        comments             7,530
  tracks.release_date 26,129

created_at is not cosmetic here: the indexer replays each migrated row as
of its created_at (migrationBlockTime), and parity keys track_downloads on
it -- which is why every sampled row of that table reported as missing
rather than mismatched.

Safe for every reader. parseMigrationTimestamp tries RFC3339Nano before
RFC3339, and Go's parser accepts a fractional second against a layout that
lacks one, so event_create.go's stricter RFC3339 parse still succeeds. A
value with no fractional part formats identically under both layouts, so
rows without microseconds are untouched.

The UTC guard now matches both layouts, so reintroducing the truncating
form still trips its .UTC() half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant